/* $Id: downmix.c,v 1.15 2005/03/17 19:22:47 stebbins Exp $

   This file is part of the HandBrake source code.
   Homepage: <http://handbrake.fr/>.
   It may be used under the terms of the GNU General Public License. */

#include <string.h>
#include <stdlib.h>
#include <inttypes.h>
#include "common.h"
#include "downmix.h"

#define LVL_PLUS6DB 2.0
#define LVL_PLUS3DB 1.4142135623730951
#define LVL_3DB 0.7071067811865476
#define LVL_45DB 0.5946035575013605
#define LVL_6DB 0.5

#define LVL_SQRT_1_4 0.5
#define LVL_SQRT_3_4 0.866025404

#define HB_CH_FRONT_LEFT             0x00000001
#define HB_CH_FRONT_RIGHT            0x00000002
#define HB_CH_FRONT_CENTER           0x00000004
#define HB_CH_LOW_FREQUENCY          0x00000008
#define HB_CH_BACK_LEFT              0x00000010
#define HB_CH_BACK_RIGHT             0x00000020
#define HB_CH_BACK_CENTER            0x00000040
#define HB_CH_SIDE_LEFT              0x00000080
#define HB_CH_SIDE_RIGHT             0x00000100

#define HB_CH_SURROUND_MASK          0x000001f0
#define HB_CH_MASK                   0x000007ff

#define HB_CH_DOLBY                  0x00000800
#define HB_CH_DPLII                  0x00001000

#define DOWNMIX_MONO 0
#define DOWNMIX_STEREO 1
#define DOWNMIX_3F 2
#define DOWNMIX_2F1R 3
#define DOWNMIX_3F1R 4
#define DOWNMIX_2F2R 5
#define DOWNMIX_3F2R 6
#define DOWNMIX_3F4R 7
#define DOWNMIX_DOLBY 8
#define DOWNMIX_DPLII 9
#define DOWNMIX_NUM_MODES 10

#define DOWNMIX_CHANNEL_MASK 0x0f

#define DOWNMIX_LFE_FLAG 0x10
#define DOWNMIX_FLAGS_MASK 0x10

hb_sample_t downmix_matrix[DOWNMIX_NUM_MODES][DOWNMIX_NUM_MODES][8][8] =
{
// MONO in
{
    // MONO out
    { { 1, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 1, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 } },
    // STEREO out
    { { LVL_3DB, LVL_3DB, 0, 0, 0, 0, 0, 0 },
      { 0,       0,       1, 0, 0, 0, 0, 0 },
      { 0,       0,       0, 0, 0, 0, 0, 0 },
      { 0,       0,       0, 0, 0, 0, 0, 0 },
      { 0,       0,       0, 0, 0, 0, 0, 0 },
      { 0,       0,       0, 0, 0, 0, 0, 0 },
      { 0,       0,       0, 0, 0, 0, 0, 0 },
      { 0,       0,       0, 0, 0, 0, 0, 0 } },
    // 3F out
    { { 0, LVL_3DB, LVL_3DB, 0, 0, 0, 0, 0 },
      { 0, 0,       0,       1, 0, 0, 0, 0 },
      { 0, 0,       0,       0, 0, 0, 0, 0 },
      { 0, 0,       0,       0, 0, 0, 0, 0 },
      { 0, 0,       0,       0, 0, 0, 0, 0 },
      { 0, 0,       0,       0, 0, 0, 0, 0 },
      { 0, 0,       0,       0, 0, 0, 0, 0 },
      { 0, 0,       0,       0, 0, 0, 0, 0 } },
    // 2F1R out
    { { LVL_3DB, LVL_3DB, 0, 0, 0, 0, 0, 0 },
      { 0,       0,       0, 1, 0, 0, 0, 0 },
      { 0,       0,       0, 0, 0, 0, 0, 0 },
      { 0,       0,       0, 0, 0, 0, 0, 0 },
      { 0,       0,       0, 0, 0, 0, 0, 0 },
      { 0,       0,       0, 0, 0, 0, 0, 0 },
      { 0,       0,       0, 0, 0, 0, 0, 0 },
      { 0,       0,       0, 0, 0, 0, 0, 0 } },
    // 3F1R out
    { { 0, LVL_3DB, LVL_3DB, 0, 0, 0, 0, 0 },
      { 0, 0,       0,       0, 1, 0, 0, 0 },
      { 0, 0,       0,       0, 0, 0, 0, 0 },
      { 0, 0,       0,       0, 0, 0, 0, 0 },
      { 0, 0,       0,       0, 0, 0, 0, 0 },
      { 0, 0,       0,       0, 0, 0, 0, 0 },
      { 0, 0,       0,       0, 0, 0, 0, 0 },
      { 0, 0,       0,       0, 0, 0, 0, 0 } },
    // 2F2R out
    { { LVL_3DB, LVL_3DB, 0, 0, 0, 0, 0, 0 },
      { 0,       0,       0, 0, 1, 0, 0, 0 },
      { 0,       0,       0, 0, 0, 0, 0, 0 },
      { 0,       0,       0, 0, 0, 0, 0, 0 },
      { 0,       0,       0, 0, 0, 0, 0, 0 },
      { 0,       0,       0, 0, 0, 0, 0, 0 },
      { 0,       0,       0, 0, 0, 0, 0, 0 },
      { 0,       0,       0, 0, 0, 0, 0, 0 } },
    // 3F2R out
    { { 0, LVL_3DB, LVL_3DB, 0, 0, 0, 0, 0 },
      { 0, 0,       0,       0, 0, 1, 0, 0 },
      { 0, 0,       0,       0, 0, 0, 0, 0 },
      { 0, 0,       0,       0, 0, 0, 0, 0 },
      { 0, 0,       0,       0, 0, 0, 0, 0 },
      { 0, 0,       0,       0, 0, 0, 0, 0 },
      { 0, 0,       0,       0, 0, 0, 0, 0 },
      { 0, 0,       0,       0, 0, 0, 0, 0 } },
    // 3F4R out
    { { 0, LVL_3DB, LVL_3DB, 0, 0, 0, 0, 0 },
      { 0, 0,       0,       0, 0, 0, 0, 1 },
      { 0, 0,       0,       0, 0, 0, 0, 0 },
      { 0, 0,       0,       0, 0, 0, 0, 0 },
      { 0, 0,       0,       0, 0, 0, 0, 0 },
      { 0, 0,       0,       0, 0, 0, 0, 0 },
      { 0, 0,       0,       0, 0, 0, 0, 0 },
      { 0, 0,       0,       0, 0, 0, 0, 0 } },
    // DOLBY out
    { { LVL_3DB, LVL_3DB, 0, 0, 0, 0, 0, 0 },
      { 0,       0,       1, 0, 0, 0, 0, 0 },
      { 0,       0,       0, 0, 0, 0, 0, 0 },
      { 0,       0,       0, 0, 0, 0, 0, 0 },
      { 0,       0,       0, 0, 0, 0, 0, 0 },
      { 0,       0,       0, 0, 0, 0, 0, 0 },
      { 0,       0,       0, 0, 0, 0, 0, 0 },
      { 0,       0,       0, 0, 0, 0, 0, 0 } },
    // DPLII out
    { { LVL_3DB, LVL_3DB, 0, 0, 0, 0, 0, 0 },
      { 0,       0,       1, 0, 0, 0, 0, 0 },
      { 0,       0,       0, 0, 0, 0, 0, 0 },
      { 0,       0,       0, 0, 0, 0, 0, 0 },
      { 0,       0,       0, 0, 0, 0, 0, 0 },
      { 0,       0,       0, 0, 0, 0, 0, 0 },
      { 0,       0,       0, 0, 0, 0, 0, 0 },
      { 0,       0,       0, 0, 0, 0, 0, 0 } },
},
// STEREO in
{
    // MONO out
    { { LVL_3DB, 0, 0, 0, 0, 0, 0, 0 },
      { LVL_3DB, 0, 0, 0, 0, 0, 0, 0 },
      {       0, 1, 0, 0, 0, 0, 0, 0 },
      {       0, 0, 0, 0, 0, 0, 0, 0 },
      {       0, 0, 0, 0, 0, 0, 0, 0 },
      {       0, 0, 0, 0, 0, 0, 0, 0 },
      {       0, 0, 0, 0, 0, 0, 0, 0 },
      {       0, 0, 0, 0, 0, 0, 0, 0 } },
    // STEREO out
    { { 1, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 1, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 1, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 } },
    // 3F out
    { { 0, 1, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 1, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 1, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 } },
    // 2F1R out
    { { 1, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 1, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 1, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 } },
    // 3F1R out
    { { 0, 1, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 1, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 1, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 } },
    // 2F2R out
    { { 1, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 1, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 1, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 } },
    // 3F2R out
    { { 0, 1, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 1, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 1, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 } },
    // 3F4R out
    { { 0, 1, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 1, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 1 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 } },
    // DOLBY out
    { { 1, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 1, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 1, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 } },
    // DPLII out
    { { 1, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 1, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 1, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 } },
},
// 3F in
{
    // MONO out
    { { LVL_PLUS3DB, 0, 0, 0, 0, 0, 0, 0 },
      { LVL_3DB,     0, 0, 0, 0, 0, 0, 0 },
      { LVL_3DB,     0, 0, 0, 0, 0, 0, 0 },
      { 0,           1, 0, 0, 0, 0, 0, 0 },
      { 0,           0, 0, 0, 0, 0, 0, 0 },
      { 0,           0, 0, 0, 0, 0, 0, 0 },
      { 0,           0, 0, 0, 0, 0, 0, 0 },
      { 0,           0, 0, 0, 0, 0, 0, 0 } },
    // STEREO out
    { { 1, 1, 0, 0, 0, 0, 0, 0 },
      { 1, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 1, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 1, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 } },
    // 3F out
    { { 1, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 1, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 1, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 1, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 } },
    // 2F1R out
    { { 1, 1, 0, 0, 0, 0, 0, 0 },
      { 1, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 1, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 1, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 } },
    // 3F1R out
    { { 1, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 1, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 1, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 1, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 } },
    // 2F2R out
    { { 1, 1, 0, 0, 0, 0, 0, 0 },
      { 1, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 1, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 1, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 } },
    // 3F2R out
    { { 1, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 1, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 1, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 1, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 } },
    // 3F4R out
    { { 1, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 1, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 1, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 1 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 } },
    // DOLBY out
    { { 1, 1, 0, 0, 0, 0, 0, 0 },
      { 1, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 1, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 1, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 } },
    // DPLII out
    { { 1, 1, 0, 0, 0, 0, 0, 0 },
      { 1, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 1, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 1, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 } },
},
// 2F1R in
{
    // MONO out
    { { LVL_3DB,     0, 0, 0, 0, 0, 0, 0 },
      { LVL_3DB,     0, 0, 0, 0, 0, 0, 0 },
      { LVL_3DB,     0, 0, 0, 0, 0, 0, 0 },
      { 0,           1, 0, 0, 0, 0, 0, 0 },
      { 0,           0, 0, 0, 0, 0, 0, 0 },
      { 0,           0, 0, 0, 0, 0, 0, 0 },
      { 0,           0, 0, 0, 0, 0, 0, 0 },
      { 0,           0, 0, 0, 0, 0, 0, 0 } },
    // STEREO out
    { { 1,       0,       0, 0, 0, 0, 0, 0 },
      { 0,       1,       0, 0, 0, 0, 0, 0 },
      { LVL_3DB, LVL_3DB, 0, 0, 0, 0, 0, 0 },
      { 0,       0,       1, 0, 0, 0, 0, 0 },
      { 0,       0,       0, 0, 0, 0, 0, 0 },
      { 0,       0,       0, 0, 0, 0, 0, 0 },
      { 0,       0,       0, 0, 0, 0, 0, 0 },
      { 0,       0,       0, 0, 0, 0, 0, 0 } },
    // 3F out
    { { 0, 1,       0,       0, 0, 0, 0, 0 },
      { 0, 0,       1,       0, 0, 0, 0, 0 },
      { 0, LVL_3DB, LVL_3DB, 0, 0, 0, 0, 0 },
      { 0, 0,       0,       1, 0, 0, 0, 0 },
      { 0, 0,       0,       0, 0, 0, 0, 0 },
      { 0, 0,       0,       0, 0, 0, 0, 0 },
      { 0, 0,       0,       0, 0, 0, 0, 0 },
      { 0, 0,       0,       0, 0, 0, 0, 0 } },
    // 2F1R out
    { { 1, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 1, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 1, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 1, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 } },
    // 3F1R out
    { { 0, 1, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 1, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 1, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 1, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 } },
    // 2F2R out
    { { 1, 0, 0,       0,       0, 0, 0, 0 },
      { 0, 1, 0,       0,       0, 0, 0, 0 },
      { 0, 0, LVL_3DB, LVL_3DB, 0, 0, 0, 0 },
      { 0, 0, 0,       0,       1, 0, 0, 0 },
      { 0, 0, 0,       0,       0, 0, 0, 0 },
      { 0, 0, 0,       0,       0, 0, 0, 0 },
      { 0, 0, 0,       0,       0, 0, 0, 0 },
      { 0, 0, 0,       0,       0, 0, 0, 0 } },
    // 3F2R out
    { { 0, 1, 0, 0,       0,       0, 0, 0 },
      { 0, 0, 1, 0,       0,       0, 0, 0 },
      { 0, 0, 0, LVL_3DB, LVL_3DB, 0, 0, 0 },
      { 0, 0, 0, 0,       0,       1, 0, 0 },
      { 0, 0, 0, 0,       0,       0, 0, 0 },
      { 0, 0, 0, 0,       0,       0, 0, 0 },
      { 0, 0, 0, 0,       0,       0, 0, 0 },
      { 0, 0, 0, 0,       0,       0, 0, 0 } },
    // 3F4R out
    { { 0, 1, 0, 0, 0, 0,       0      , 0 },
      { 0, 0, 1, 0, 0, 0,       0      , 0 },
      { 0, 0, 0, 0, 0, LVL_3DB, LVL_3DB, 0 },
      { 0, 0, 0, 0, 0, 0,       0      , 1 },
      { 0, 0, 0, 0, 0, 0,       0      , 0 },
      { 0, 0, 0, 0, 0, 0,       0      , 0 },
      { 0, 0, 0, 0, 0, 0,       0      , 0 },
      { 0, 0, 0, 0, 0, 0,       0      , 0 } },
    // DOLBY out
    { { 1,        0,       0, 0, 0, 0, 0, 0 },
      { 0,        1,       0, 0, 0, 0, 0, 0 },
      { -LVL_3DB, LVL_3DB, 0, 0, 0, 0, 0, 0 },
      { 0,        0,       1, 0, 0, 0, 0, 0 },
      { 0,        0,       0, 0, 0, 0, 0, 0 },
      { 0,        0,       0, 0, 0, 0, 0, 0 },
      { 0,        0,       0, 0, 0, 0, 0, 0 },
      { 0,        0,       0, 0, 0, 0, 0, 0 } },
    // DPLII out
    { { 1,        0,       0, 0, 0, 0, 0, 0 },
      { 0,        1,       0, 0, 0, 0, 0, 0 },
      { -LVL_3DB, LVL_3DB, 0, 0, 0, 0, 0, 0 },
      { 0,        0,       1, 0, 0, 0, 0, 0 },
      { 0,        0,       0, 0, 0, 0, 0, 0 },
      { 0,        0,       0, 0, 0, 0, 0, 0 },
      { 0,        0,       0, 0, 0, 0, 0, 0 },
      { 0,        0,       0, 0, 0, 0, 0, 0 } },
},
// 3F1R in
{
    // MONO out
    { { LVL_PLUS3DB, 0, 0, 0, 0, 0, 0, 0 },
      { LVL_3DB,     0, 0, 0, 0, 0, 0, 0 },
      { LVL_3DB,     0, 0, 0, 0, 0, 0, 0 },
      { LVL_3DB,     0, 0, 0, 0, 0, 0, 0 },
      { 0,           1, 0, 0, 0, 0, 0, 0 },
      { 0,           0, 0, 0, 0, 0, 0, 0 },
      { 0,           0, 0, 0, 0, 0, 0, 0 },
      { 0,           0, 0, 0, 0, 0, 0, 0 } },
    // STEREO out
    { { 1,       1,       0, 0, 0, 0, 0, 0 },
      { 1,       0,       0, 0, 0, 0, 0, 0 },
      { 0,       1,       0, 0, 0, 0, 0, 0 },
      { LVL_3DB, LVL_3DB, 0, 0, 0, 0, 0, 0 },
      { 0,       0,       1, 0, 0, 0, 0, 0 },
      { 0,       0,       0, 0, 0, 0, 0, 0 },
      { 0,       0,       0, 0, 0, 0, 0, 0 },
      { 0,       0,       0, 0, 0, 0, 0, 0 } },
    // 3F out
    { { 1, 0,       0,       0, 0, 0, 0, 0 },
      { 0, 1,       0,       0, 0, 0, 0, 0 },
      { 0, 0,       1,       0, 0, 0, 0, 0 },
      { 0, LVL_3DB, LVL_3DB, 0, 0, 0, 0, 0 },
      { 0, 0,       0,       1, 0, 0, 0, 0 },
      { 0, 0,       0,       0, 0, 0, 0, 0 },
      { 0, 0,       0,       0, 0, 0, 0, 0 },
      { 0, 0,       0,       0, 0, 0, 0, 0 } },
    // 2F1R out
    { { 1, 1, 0, 0, 0, 0, 0, 0 },
      { 1, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 1, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 1, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 1, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 } },
    // 3F1R out
    { { 1, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 1, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 1, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 1, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 1, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 } },
    // 2F2R out
    { { 1, 1, 0,       0,       0, 0, 0, 0 },
      { 1, 0, 0,       0,       0, 0, 0, 0 },
      { 0, 1, 0,       0,       0, 0, 0, 0 },
      { 0, 0, LVL_3DB, LVL_3DB, 0, 0, 0, 0 },
      { 0, 0, 0,       0,       1, 0, 0, 0 },
      { 0, 0, 0,       0,       0, 0, 0, 0 },
      { 0, 0, 0,       0,       0, 0, 0, 0 },
      { 0, 0, 0,       0,       0, 0, 0, 0 } },
    // 3F2R out
    { { 1, 0, 0, 0,       0,       0, 0, 0 },
      { 0, 1, 0, 0,       0,       0, 0, 0 },
      { 0, 0, 1, 0,       0,       0, 0, 0 },
      { 0, 0, 0, LVL_3DB, LVL_3DB, 0, 0, 0 },
      { 0, 0, 0, 0,       0,       1, 0, 0 },
      { 0, 0, 0, 0,       0,       0, 0, 0 },
      { 0, 0, 0, 0,       0,       0, 0, 0 },
      { 0, 0, 0, 0,       0,       0, 0, 0 } },
    // 3F4R out
    { { 1, 0, 0, 0, 0, 0,       0      , 0 },
      { 0, 1, 0, 0, 0, 0,       0      , 0 },
      { 0, 0, 1, 0, 0, 0,       0      , 0 },
      { 0, 0, 0, 0, 0, LVL_3DB, LVL_3DB, 0 },
      { 0, 0, 0, 0, 0, 0,       0      , 1 },
      { 0, 0, 0, 0, 0, 0,       0      , 0 },
      { 0, 0, 0, 0, 0, 0,       0      , 0 },
      { 0, 0, 0, 0, 0, 0,       0      , 0 } },
    // DOLBY out
    { { LVL_3DB,  LVL_3DB, 0, 0, 0, 0, 0, 0 },
      { 1,        0,       0, 0, 0, 0, 0, 0 },
      { 0,        1,       0, 0, 0, 0, 0, 0 },
      { -LVL_3DB, LVL_3DB, 0, 0, 0, 0, 0, 0 },
      { 0,        0,       1, 0, 0, 0, 0, 0 },
      { 0,        0,       0, 0, 0, 0, 0, 0 },
      { 0,        0,       0, 0, 0, 0, 0, 0 },
      { 0,        0,       0, 0, 0, 0, 0, 0 } },
    // DPLII out
    { { LVL_3DB,  LVL_3DB, 0, 0, 0, 0, 0, 0 },
      { 1,        0,       0, 0, 0, 0, 0, 0 },
      { 0,        1,       0, 0, 0, 0, 0, 0 },
      { -LVL_3DB, LVL_3DB, 0, 0, 0, 0, 0, 0 },
      { 0,        0,       1, 0, 0, 0, 0, 0 },
      { 0,        0,       0, 0, 0, 0, 0, 0 },
      { 0,        0,       0, 0, 0, 0, 0, 0 },
      { 0,        0,       0, 0, 0, 0, 0, 0 } },
},
// 2F2R in
{
    // MONO out
    { { LVL_3DB, 0, 0, 0, 0, 0, 0, 0 },
      { LVL_3DB, 0, 0, 0, 0, 0, 0, 0 },
      { LVL_3DB, 0, 0, 0, 0, 0, 0, 0 },
      { LVL_3DB, 0, 0, 0, 0, 0, 0, 0 },
      { 0,       1, 0, 0, 0, 0, 0, 0 },
      { 0,       0, 0, 0, 0, 0, 0, 0 },
      { 0,       0, 0, 0, 0, 0, 0, 0 },
      { 0,       0, 0, 0, 0, 0, 0, 0 } },
    // STEREO out
    { { 1, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 1, 0, 0, 0, 0, 0, 0 },
      { 1, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 1, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 1, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 } },
    // 3F out
    { { 0, 1, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 1, 0, 0, 0, 0, 0 },
      { 0, 1, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 1, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 1, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 } },
    // 2F1R out
    { { 1, 0, 0,       0, 0, 0, 0, 0 },
      { 0, 1, 0,       0, 0, 0, 0, 0 },
      { 0, 0, LVL_3DB, 0, 0, 0, 0, 0 },
      { 0, 0, LVL_3DB, 0, 0, 0, 0, 0 },
      { 0, 0, 0,       1, 0, 0, 0, 0 },
      { 0, 0, 0,       0, 0, 0, 0, 0 },
      { 0, 0, 0,       0, 0, 0, 0, 0 },
      { 0, 0, 0,       0, 0, 0, 0, 0 } },
    // 3F1R out
    { { 0, 1, 0, 0,       0, 0, 0, 0 },
      { 0, 0, 1, 0,       0, 0, 0, 0 },
      { 0, 0, 0, LVL_3DB, 0, 0, 0, 0 },
      { 0, 0, 0, LVL_3DB, 0, 0, 0, 0 },
      { 0, 0, 0, 0,       1, 0, 0, 0 },
      { 0, 0, 0, 0,       0, 0, 0, 0 },
      { 0, 0, 0, 0,       0, 0, 0, 0 },
      { 0, 0, 0, 0,       0, 0, 0, 0 } },
    // 2F2R out
    { { 1, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 1, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 1, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 1, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 1, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 } },
    // 3F2R out
    { { 0, 1, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 1, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 1, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 1, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 1, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 } },
    // 3F4R out
    { { 0, 1, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 1, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 1, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 1, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 1 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 } },
    // DOLBY out
    { { 1,        0,       0, 0, 0, 0, 0, 0 },
      { 0,        1,       0, 0, 0, 0, 0, 0 },
      { -LVL_6DB, LVL_6DB, 0, 0, 0, 0, 0, 0 },
      { -LVL_6DB, LVL_6DB, 0, 0, 0, 0, 0, 0 },
      { 0,        0,       1, 0, 0, 0, 0, 0 },
      { 0,        0,       0, 0, 0, 0, 0, 0 },
      { 0,        0,       0, 0, 0, 0, 0, 0 },
      { 0,        0,       0, 0, 0, 0, 0, 0 } },
    // DPLII out
    { { 1,             0,             0, 0, 0, 0, 0, 0 },
      { 0,             1,             0, 0, 0, 0, 0, 0 },
      { -LVL_SQRT_3_4, LVL_SQRT_1_4,  0, 0, 0, 0, 0, 0 },
      { -LVL_SQRT_1_4, LVL_SQRT_3_4,  0, 0, 0, 0, 0, 0 },
      { 0,             0,             1, 0, 0, 0, 0, 0 },
      { 0,             0,             0, 0, 0, 0, 0, 0 },
      { 0,             0,             0, 0, 0, 0, 0, 0 },
      { 0,             0,             0, 0, 0, 0, 0, 0 } },
},
// 3F2R in
{
    // MONO out
    { { LVL_PLUS3DB, 0, 0, 0, 0, 0, 0, 0 },
      { LVL_3DB,     0, 0, 0, 0, 0, 0, 0 },
      { LVL_3DB,     0, 0, 0, 0, 0, 0, 0 },
      { LVL_3DB,     0, 0, 0, 0, 0, 0, 0 },
      { LVL_3DB,     0, 0, 0, 0, 0, 0, 0 },
      { 0,           1, 0, 0, 0, 0, 0, 0 },
      { 0,           0, 0, 0, 0, 0, 0, 0 },
      { 0,           0, 0, 0, 0, 0, 0, 0 } },
    // STEREO out
    { { 1, 1, 0, 0, 0, 0, 0, 0 },
      { 1, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 1, 0, 0, 0, 0, 0, 0 },
      { 1, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 1, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 1, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 } },
    // 3F out
    { { 1, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 1, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 1, 0, 0, 0, 0, 0 },
      { 0, 1, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 1, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 1, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 } },
    // 2F1R out
    { { 1, 1, 0,       0, 0, 0, 0, 0 },
      { 1, 0, 0,       0, 0, 0, 0, 0 },
      { 0, 1, 0,       0, 0, 0, 0, 0 },
      { 0, 0, LVL_3DB, 0, 0, 0, 0, 0 },
      { 0, 0, LVL_3DB, 0, 0, 0, 0, 0 },
      { 0, 0, 0,       1, 0, 0, 0, 0 },
      { 0, 0, 0,       0, 0, 0, 0, 0 },
      { 0, 0, 0,       0, 0, 0, 0, 0 } },
    // 3F1R out
    { { 1, 0, 0, 0,       0, 0, 0, 0 },
      { 0, 1, 0, 0,       0, 0, 0, 0 },
      { 0, 0, 1, 0,       0, 0, 0, 0 },
      { 0, 0, 0, LVL_3DB, 0, 0, 0, 0 },
      { 0, 0, 0, LVL_3DB, 0, 0, 0, 0 },
      { 0, 0, 0, 0,       1, 0, 0, 0 },
      { 0, 0, 0, 0,       0, 0, 0, 0 },
      { 0, 0, 0, 0,       0, 0, 0, 0 } },
    // 2F2R out
    { { 1, 1, 0, 0, 0, 0, 0, 0 },
      { 1, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 1, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 1, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 1, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 1, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 } },
    // 3F2R out
    { { 1, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 1, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 1, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 1, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 1, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 1, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 } },
    // 3F4R out
    { { 1, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 1, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 1, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 1, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 1, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 1 },
      { 0, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 0 } },
    // DOLBY out
    { { LVL_3DB,  LVL_3DB, 0, 0, 0, 0, 0, 0 },
      { 1,        0,       0, 0, 0, 0, 0, 0 },
      { 0,        1,       0, 0, 0, 0, 0, 0 },
      { -LVL_3DB, LVL_3DB, 0, 0, 0, 0, 0, 0 },
      { -LVL_3DB, LVL_3DB, 0, 0, 0, 0, 0, 0 },
      { 0,        0,       1, 0, 0, 0, 0, 0 },
      { 0,        0,       0, 0, 0, 0, 0, 0 },
      { 0,        0,       0, 0, 0, 0, 0, 0 } },
    // DPLII out
    { { LVL_3DB,       LVL_3DB,       0, 0, 0, 0, 0, 0 },
      { 1,             0,             0, 0, 0, 0, 0, 0 },
      { 0,             1,             0, 0, 0, 0, 0, 0 },
      { -LVL_SQRT_3_4, LVL_SQRT_1_4,  0, 0, 0, 0, 0, 0 },
      { -LVL_SQRT_1_4, LVL_SQRT_3_4,  0, 0, 0, 0, 0, 0 },
      { 0,             0,             1, 0, 0, 0, 0, 0 },
      { 0,             0,             0, 0, 0, 0, 0, 0 },
      { 0,             0,             0, 0, 0, 0, 0, 0 } },
},
// 3F4R in
{
    // MONO out
    { { LVL_PLUS3DB, 0, 0, 0, 0, 0, 0, 0 },
      { LVL_3DB,     0, 0, 0, 0, 0, 0, 0 },
      { LVL_3DB,     0, 0, 0, 0, 0, 0, 0 },
      { LVL_6DB,     0, 0, 0, 0, 0, 0, 0 },
      { LVL_6DB,     0, 0, 0, 0, 0, 0, 0 },
      { LVL_6DB,     0, 0, 0, 0, 0, 0, 0 },
      { LVL_6DB,     0, 0, 0, 0, 0, 0, 0 },
      { 0,           1, 0, 0, 0, 0, 0, 0 } },
    // STEREO out
    { { 1,       1,       0, 0, 0, 0, 0, 0 },
      { 1,       0,       0, 0, 0, 0, 0, 0 },
      { 0,       1,       0, 0, 0, 0, 0, 0 },
      { LVL_3DB, 0,       0, 0, 0, 0, 0, 0 },
      { 0,       LVL_3DB, 0, 0, 0, 0, 0, 0 },
      { LVL_3DB, 0,       0, 0, 0, 0, 0, 0 },
      { 0,       LVL_3DB, 0, 0, 0, 0, 0, 0 },
      { 0,       0,       1, 0, 0, 0, 0, 0 } },
    // 3F out
    { { 1, 0,       0,       0, 0, 0, 0, 0 },
      { 0, 1,       0,       0, 0, 0, 0, 0 },
      { 0, 0,       1,       0, 0, 0, 0, 0 },
      { 0, LVL_3DB, 0,       0, 0, 0, 0, 0 },
      { 0, 0,       LVL_3DB, 0, 0, 0, 0, 0 },
      { 0, LVL_3DB, 0,       0, 0, 0, 0, 0 },
      { 0, 0,       LVL_3DB, 0, 0, 0, 0, 0 },
      { 0, 0,       0,       1, 0, 0, 0, 0 } },
    // 2F1R out
    { { 1, 1, 0,       0, 0, 0, 0, 0 },
      { 1, 0, 0,       0, 0, 0, 0, 0 },
      { 0, 1, 0,       0, 0, 0, 0, 0 },
      { 0, 0, LVL_6DB, 0, 0, 0, 0, 0 },
      { 0, 0, LVL_6DB, 0, 0, 0, 0, 0 },
      { 0, 0, LVL_6DB, 0, 0, 0, 0, 0 },
      { 0, 0, LVL_6DB, 0, 0, 0, 0, 0 },
      { 0, 0, 0,       1, 0, 0, 0, 0 } },
    // 3F1R out
    { { 1, 0, 0, 0,       0, 0, 0, 0 },
      { 0, 1, 0, 0,       0, 0, 0, 0 },
      { 0, 0, 1, 0,       0, 0, 0, 0 },
      { 0, 0, 0, LVL_6DB, 0, 0, 0, 0 },
      { 0, 0, 0, LVL_6DB, 0, 0, 0, 0 },
      { 0, 0, 0, LVL_6DB, 0, 0, 0, 0 },
      { 0, 0, 0, LVL_6DB, 0, 0, 0, 0 },
      { 0, 0, 0, 0,       1, 0, 0, 0 } },
    // 2F2R out
    { { 1, 1, 0,       0,       0, 0, 0, 0 },
      { 1, 0, 0,       0,       0, 0, 0, 0 },
      { 0, 1, 0,       0,       0, 0, 0, 0 },
      { 0, 0, LVL_3DB, 0,       0, 0, 0, 0 },
      { 0, 0, 0,       LVL_3DB, 0, 0, 0, 0 },
      { 0, 0, LVL_3DB, 0,       0, 0, 0, 0 },
      { 0, 0, 0,       LVL_3DB, 0, 0, 0, 0 },
      { 0, 0, 0,       0,       1, 0, 0, 0 } },
    // 3F2R out
    { { 1, 0, 0, 0,       0,       0, 0, 0 },
      { 0, 1, 0, 0,       0,       0, 0, 0 },
      { 0, 0, 1, 0,       0,       0, 0, 0 },
      { 0, 0, 0, LVL_3DB, 0,       0, 0, 0 },
      { 0, 0, 0, 0,       LVL_3DB, 0, 0, 0 },
      { 0, 0, 0, LVL_3DB, 0,       0, 0, 0 },
      { 0, 0, 0, 0,       LVL_3DB, 0, 0, 0 },
      { 0, 0, 0, 0,       0,       1, 0, 0 } },
    // 3F4R out
    { { 1, 0, 0, 0, 0, 0, 0, 0 },
      { 0, 1, 0, 0, 0, 0, 0, 0 },
      { 0, 0, 1, 0, 0, 0, 0, 0 },
      { 0, 0, 0, 1, 0, 0, 0, 0 },
      { 0, 0, 0, 0, 1, 0, 0, 0 },
      { 0, 0, 0, 0, 0, 1, 0, 0 },
      { 0, 0, 0, 0, 0, 0, 1, 0 },
      { 0, 0, 0, 0, 0, 0, 0, 1 } },
    // DOLBY out
    { { LVL_3DB,  LVL_3DB, 0, 0, 0, 0, 0, 0 },
      { 1,        0,       0, 0, 0, 0, 0, 0 },
      { 0,        1,       0, 0, 0, 0, 0, 0 },
      { -LVL_3DB, LVL_3DB, 0, 0, 0, 0, 0, 0 },
      { -LVL_3DB, LVL_3DB, 0, 0, 0, 0, 0, 0 },
      { -LVL_3DB, LVL_3DB, 0, 0, 0, 0, 0, 0 },
      { -LVL_3DB, LVL_3DB, 0, 0, 0, 0, 0, 0 },
      { 0,        0,       1, 0, 0, 0, 0, 0 } },
    // DPLII out
    { { LVL_3DB,               LVL_3DB,               0, 0, 0, 0, 0, 0 },
      { 1,                     0,                     0, 0, 0, 0, 0, 0 },
      { 0,                     1,                     0, 0, 0, 0, 0, 0 },
      { -LVL_SQRT_3_4*LVL_3DB, LVL_SQRT_1_4*LVL_3DB,  0, 0, 0, 0, 0, 0 },
      { -LVL_SQRT_1_4*LVL_3DB, LVL_SQRT_3_4*LVL_3DB,  0, 0, 0, 0, 0, 0 },
      { -LVL_SQRT_3_4*LVL_3DB, LVL_SQRT_1_4*LVL_3DB,  0, 0, 0, 0, 0, 0 },
      { -LVL_SQRT_1_4*LVL_3DB, LVL_SQRT_3_4*LVL_3DB,  0, 0, 0, 0, 0, 0 },
      { 0,                     0,                     1, 0, 0, 0, 0, 0 } }
},
};

static int channel_layout_map[DOWNMIX_NUM_MODES] =
{
    // DOWNMIX_MONO 
    (HB_CH_FRONT_CENTER),
    // DOWNMIX_STEREO 
    (HB_CH_FRONT_LEFT|HB_CH_FRONT_RIGHT),
    // DOWNMIX_3F 
    (HB_CH_FRONT_LEFT|HB_CH_FRONT_RIGHT|HB_CH_FRONT_CENTER),
    // DOWNMIX_2F1R 
    (HB_CH_FRONT_LEFT|HB_CH_FRONT_RIGHT|HB_CH_BACK_CENTER),
    // DOWNMIX_3F1R 
    (HB_CH_FRONT_LEFT|HB_CH_FRONT_RIGHT|HB_CH_FRONT_CENTER|HB_CH_BACK_CENTER),
    // DOWNMIX_2F2R 
    (HB_CH_FRONT_LEFT|HB_CH_FRONT_RIGHT|HB_CH_BACK_LEFT|HB_CH_BACK_RIGHT),
    // DOWNMIX_3F2R 
    (HB_CH_FRONT_LEFT|HB_CH_FRONT_RIGHT|HB_CH_FRONT_CENTER|HB_CH_BACK_LEFT|HB_CH_BACK_RIGHT),
    // DOWNMIX_3F4R 
    (HB_CH_FRONT_LEFT|HB_CH_FRONT_RIGHT|HB_CH_FRONT_CENTER|HB_CH_SIDE_LEFT|
     HB_CH_SIDE_RIGHT|HB_CH_BACK_LEFT|HB_CH_BACK_RIGHT),
    // DOWNMIX_DOLBY 
    (HB_CH_FRONT_LEFT|HB_CH_FRONT_RIGHT),
    // DOWNMIX_DPLII 
    (HB_CH_FRONT_LEFT|HB_CH_FRONT_RIGHT)
};

int hb_layout_to_mode(int layout)
{
    int mode;
    switch (layout & HB_INPUT_CH_LAYOUT_DISCRETE_NO_LFE_MASK)
    {
        case HB_INPUT_CH_LAYOUT_MONO:
            mode = DOWNMIX_MONO;
            break;
        case HB_INPUT_CH_LAYOUT_STEREO:
            mode = DOWNMIX_STEREO;
            break;
        case HB_INPUT_CH_LAYOUT_3F:
            mode = DOWNMIX_3F;
            break;
        case HB_INPUT_CH_LAYOUT_2F1R:
            mode = DOWNMIX_2F1R;
            break;
        case HB_INPUT_CH_LAYOUT_3F1R:
            mode = DOWNMIX_3F1R;
            break;
        case HB_INPUT_CH_LAYOUT_2F2R:
            mode = DOWNMIX_2F2R;
            break;
        case HB_INPUT_CH_LAYOUT_3F2R:
            mode = DOWNMIX_3F2R;
            break;
        case HB_INPUT_CH_LAYOUT_4F2R:
            mode = DOWNMIX_3F2R|DOWNMIX_LFE_FLAG;
            break;
        case HB_INPUT_CH_LAYOUT_3F4R:
            mode = DOWNMIX_3F4R;
            break;
        case HB_INPUT_CH_LAYOUT_DOLBY:
            mode = DOWNMIX_STEREO;
            break;
        default:
            mode = DOWNMIX_STEREO;
            break;
    }
    if (layout & HB_INPUT_CH_LAYOUT_DISCRETE_LFE_MASK)
        mode |= DOWNMIX_LFE_FLAG;
    return mode;
}

int hb_mixdown_to_mode(uint32_t mixdown)
{
    switch (mixdown)
    {
        case HB_AMIXDOWN_MONO:
            return DOWNMIX_MONO;
        case HB_AMIXDOWN_STEREO:
            return DOWNMIX_STEREO;
        case HB_AMIXDOWN_DOLBY:
            return DOWNMIX_DOLBY;
        case HB_AMIXDOWN_DOLBYPLII:
            return DOWNMIX_DPLII;
        case HB_AMIXDOWN_6CH:
            return DOWNMIX_3F2R|DOWNMIX_LFE_FLAG;
        default:
            return DOWNMIX_STEREO;
    }
}


// ffmpeg gives us SMPTE channel layout
// We could use this layout and remap channels in encfaac,
// but VLC may have problems with remapping, so lets
// allow remapping to the default QuickTime order which is:
//
// C   L   R   LS  RS  Rls Rrs LFE
//
// This arrangement also makes it possible to use half as
// many downmix matrices since the matrix with and without
// LFE are the same.
//
// Use hb_layout_remap to accomplish this.  For convenience
// I've provided the necessary maps.
//
// SMPTE channel layout
//
// DUAL-MONO      L   R 
// DUAL-MONO-LFE  L   R   LFE
// MONO           M
// MONO-LFE       M   LFE
// STEREO         L   R 
// STEREO-LFE     L   R   LFE
// 3F             L   R   C
// 3F-LFE         L   R   C    LFE
// 2F1            L   R   S
// 2F1-LFE        L   R   LFE  S
// 3F1            L   R   C    S
// 3F1-LFE        L   R   C    LFE S
// 2F2            L   R   LS   RS
// 2F2-LFE        L   R   LFE  LS   RS
// 3F2            L   R   C    LS   RS
// 3F2-LFE        L   R   C    LFE  LS   RS
// 3F4            L   R   C    Rls  Rrs  LS   RS
// 3F4-LFE        L   R   C    LFE  Rls  Rrs  LS   RS
//

#define CH_C      0
#define CH_L      1
#define CH_R      2
#define CH_CS     3
#define CH_LS     3
#define CH_RS     4
#define CH_Rls    5
#define CH_Rrs    6
#define CH_LFE    7

hb_chan_map_t hb_qt_chan_map =
{
{
    {{ CH_C,                                                     },
     { CH_C, CH_LFE,                                             }}, // MONO

    {{ CH_L, CH_R,                                               },
     { CH_L, CH_R, CH_LFE,                                       }}, // STEREO

    {{ CH_C, CH_L, CH_R,                                         },
     { CH_C, CH_L, CH_R, CH_LFE,                                 }}, // 3F

    {{ CH_L, CH_R, CH_CS,                                        },
     { CH_L, CH_R, CH_CS, CH_LFE,                                }}, // 2F1R

    {{ CH_C, CH_L, CH_R, CH_CS,                                  },
     { CH_C, CH_L, CH_R, CH_CS, CH_LFE,                          }}, // 3F1R

    {{ CH_L, CH_R, CH_LS, CH_RS,                                 },
     { CH_L, CH_R, CH_LS, CH_RS, CH_LFE,                         }}, // 2F2R

    {{ CH_C, CH_L, CH_R, CH_LS, CH_RS,                           },
     { CH_C, CH_L, CH_R, CH_LS, CH_RS, CH_LFE,                   }}, // 3F2R

    {{ CH_C, CH_L, CH_R, CH_LS, CH_RS,  CH_Rls,  CH_Rrs,         },
     { CH_C, CH_L, CH_R, CH_LS, CH_RS,  CH_Rls,  CH_Rrs,  CH_LFE }}, // 3F4R

    {{ CH_L, CH_R,                                               },
     { CH_L, CH_R,                                               }}, // DOLBY

    {{ CH_L, CH_R,                                               },
     { CH_L, CH_R,                                               }}  // DPLII
},
{
    // CH_C  CH_L  CH_R  CH_LS/CS  CH_RS   CH_Rls   CH_Rrs   CH_LFE 
    {{ 0,    0,    0,    0,        0,      0,       0,       0   },
     { 0,    0,    0,    0,        0,      0,       0,       1   }}, // MONO

    {{ 0,    0,    1,    0,        0,      0,       0,       0   },
     { 0,    0,    1,    0,        0,      0,       0,       2   }}, // STEREO

    {{ 0,    1,    2,    0,        0,      0,       0,       0   },
     { 0,    1,    2,    0,        0,      0,       0,       3   }}, // 3F

    {{ 0,    0,    1,    2,        0,      0,       0,       0   },
     { 0,    0,    1,    2,        0,      0,       0,       3   }}, // 2F1R

    {{ 0,    1,    2,    3,        0,      0,       0,       0   },
     { 0,    1,    2,    3,        0,      0,       0,       4   }}, // 3F1R

    {{ 0,    0,    1,    2,        3,      0,       0,       0   },
     { 0,    0,    1,    2,        3,      0,       0,       4   }}, // 2F2R

    {{ 0,    1,    2,    3,        4,      0,       0,       0   },
     { 0,    1,    2,    3,        4,      0,       0,       5   }}, // 3F2R

    {{ 0,    1,    2,    3,        4,      5,       6,       0   },
     { 0,    1,    2,    3,        4,      5,       6,       7   }}, // 3F4R

    {{ 0,    0,    1,    0,        0,      0,       0,       0   },
     { 0,    0,    1,    0,        0,      0,       0,       0   }}, // DOLBY

    {{ 0,    0,    1,    0,        0,      0,       0,       0   },
     { 0,    0,    1,    0,        0,      0,       0,       0   }}  // DPLII
}
};

hb_chan_map_t hb_smpte_chan_map =
{
{
    {{ CH_C,                                                     },
     { CH_C, CH_LFE,                                             }}, // MONO

    {{ CH_L, CH_R,                                               },
     { CH_L, CH_R, CH_LFE,                                       }}, // STEREO

    {{ CH_L, CH_R, CH_C,                                         },
     { CH_L, CH_R, CH_C, CH_LFE,                                 }}, // 3F

    {{ CH_L, CH_R, CH_CS,                                        },
     { CH_L, CH_R, CH_LFE, CH_CS,                                }}, // 2F1R

    {{ CH_L, CH_R, CH_C,   CH_CS,                                },
     { CH_L, CH_R, CH_LFE, CH_CS,                                }}, // 3F1R

    {{ CH_L, CH_R, CH_LS,  CH_RS,                                },
     { CH_L, CH_R, CH_LFE, CH_LS, CH_RS,                         }}, // 2F2R

    {{ CH_L, CH_R, CH_C, CH_LS,  CH_RS,                          },
     { CH_L, CH_R, CH_C, CH_LFE, CH_LS, CH_RS,                   }}, // 3F2R

    {{ CH_L, CH_R, CH_C, CH_Rls, CH_Rrs, CH_LS, CH_RS },
     { CH_L, CH_R, CH_C, CH_LFE, CH_Rls, CH_Rrs, CH_LS, CH_RS    }}, // 3F4R

    {{ CH_L, CH_R,                                               },
     { CH_L, CH_R,                                               }}, // DOLBY

    {{ CH_L, CH_R,                                               },
     { CH_L, CH_R,                                               }}  // DPLII
},
{
    // CH_C  CH_L  CH_R  CH_LS/CS  CH_RS   CH_Rls   CH_Rrs   CH_LFE 
    {{ 0,    0,    0,    0,        0,      0,       0,       0   },
     { 0,    0,    0,    0,        0,      0,       0,       1   }}, // MONO

    {{ 0,    0,    1,    0,        0,      0,       0,       0   },
     { 0,    0,    1,    0,        0,      0,       0,       2   }}, // STEREO

    {{ 2,    0,    1,    0,        0,      0,       0,       0   },
     { 2,    0,    1,    0,        0,      0,       0,       3   }}, // 3F

    {{ 0,    0,    1,    2,        0,      0,       0,       0   },
     { 0,    0,    1,    3,        0,      0,       0,       2   }}, // 2F1R

    {{ 2,    0,    1,    3,        0,      0,       0,       0   },
     { 2,    0,    1,    4,        0,      0,       0,       3   }}, // 3F1R

    {{ 0,    0,    1,    2,        3,      0,       0,       0   },
     { 0,    0,    1,    3,        4,      0,       0,       2   }}, // 2F2R

    {{ 2,    0,    1,    3,        4,      0,       0,       0   },
     { 2,    0,    1,    4,        5,      0,       0,       3   }}, // 3F2R

    {{ 2,    0,    1,    5,        6,      3,       4,       0   },
     { 2,    0,    1,    6,        7,      4,       5,       3   }}, // 3F4R

    {{ 0,    0,    1,    0,        0,      0,       0,       0   },
     { 0,    0,    1,    0,        0,      0,       0,       0   }}, // DOLBY

    {{ 0,    0,    1,    0,        0,      0,       0,       0   },
     { 0,    0,    1,    0,        0,      0,       0,       0   }}  // DPLII
}
};

hb_chan_map_t hb_ac3_chan_map =
{
{
    {{ CH_C,                                                     },
     { CH_LFE, CH_C,                                             }}, // MONO

    {{ CH_L, CH_R,                                               },
     { CH_LFE, CH_L, CH_R,                                       }}, // STEREO

    {{ CH_L, CH_C, CH_R,                                         },
     { CH_LFE, CH_L, CH_C, CH_R,                                 }}, // 3F

    {{ CH_L, CH_R, CH_CS,                                        },
     { CH_LFE, CH_L, CH_R, CH_CS,                                }}, // 2F1R

    {{ CH_L, CH_C, CH_R, CH_CS,                                  },
     { CH_LFE, CH_L, CH_C, CH_R, CH_CS,                          }}, // 3F1R

    {{ CH_L, CH_R, CH_LS, CH_RS,                                 },
     { CH_LFE, CH_L, CH_R, CH_LS, CH_RS,                         }}, // 2F2R

    {{ CH_L, CH_C, CH_R, CH_LS, CH_RS,                           },
     { CH_LFE, CH_L, CH_C, CH_R, CH_LS, CH_RS,                   }}, // 3F2R

    {{ CH_L, CH_C, CH_R, CH_LS, CH_RS,  CH_Rls,  CH_Rrs,         },
     { CH_LFE, CH_L, CH_C, CH_R, CH_LS, CH_RS,  CH_Rls,  CH_Rrs  }}, // 3F4R

    {{ CH_L, CH_R,                                               },
     { CH_L, CH_R,                                               }}, // DOLBY

    {{ CH_L, CH_R,                                               },
     { CH_L, CH_R,                                               }}  // DPLII
},
{
    // CH_C  CH_L  CH_R  CH_LS/CS  CH_RS   CH_Rls   CH_Rrs   CH_LFE 
    {{ 0,    0,    0,    0,        0,      0,       0,       0   },
     { 1,    0,    0,    0,        0,      0,       0,       0   }}, // MONO

    {{ 0,    0,    1,    0,        0,      0,       0,       0   },
     { 0,    1,    2,    0,        0,      0,       0,       0   }}, // STEREO

    {{ 1,    0,    2,    0,        0,      0,       0,       0   },
     { 2,    1,    3,    0,        0,      0,       0,       0   }}, // 3F

    {{ 0,    0,    1,    2,        0,      0,       0,       0   },
     { 0,    1,    2,    3,        0,      0,       0,       0   }}, // 2F1R

    {{ 1,    0,    2,    3,        0,      0,       0,       0   },
     { 2,    1,    3,    4,        0,      0,       0,       0   }}, // 3F1R

    {{ 0,    0,    1,    2,        3,      0,       0,       0   },
     { 0,    1,    2,    3,        4,      0,       0,       0   }}, // 2F2R

    {{ 1,    0,    2,    3,        4,      0,       0,       0   },
     { 2,    1,    3,    4,        5,      0,       0,       0   }}, // 3F2R

    {{ 1,    0,    2,    3,        4,      5,       6,       0   },
     { 2,    1,    3,    4,        5,      6,       7,       0   }}, // 3F4R

    {{ 0,    0,    1,    0,        0,      0,       0,       0   },
     { 0,    0,    1,    0,        0,      0,       0,       0   }}, // DOLBY

    {{ 0,    0,    1,    0,        0,      0,       0,       0   },
     { 0,    0,    1,    0,        0,      0,       0,       0   }}  // DPLII
}
};

static const uint8_t nchans_tbl[] = {1, 2, 3, 3, 4, 4, 5, 7, 2, 2};

// Takes a set of samples and remaps the channel layout
void hb_layout_remap( 
    hb_chan_map_t * map_in, 
    hb_chan_map_t * map_out, 
    int layout, 
    hb_sample_t * samples, 
    int nsamples )
{
    int nchans;
    int ii, jj;
    int lfe;
    int * map;
    int * inv_map;
    int mode;
    hb_sample_t tmp[8];

    mode = hb_layout_to_mode(layout);
    lfe = ((mode & DOWNMIX_LFE_FLAG) != 0);
    mode = mode & DOWNMIX_CHANNEL_MASK;
    nchans = nchans_tbl[mode] + lfe;
    inv_map = map_in->inv_chan_map[mode][lfe];
    map = map_out->chan_map[mode][lfe];

    for (ii = 0; ii < nsamples; ii++)
    {
        for (jj = 0; jj < nchans; jj++)
        {
            tmp[jj] = samples[jj];
        }
        for (jj = 0; jj < nchans; jj++)
        {
            int ord = map[jj];
            samples[jj] = tmp[inv_map[ord]];
        }
        samples += nchans;
    }
}

static void matrix_mul(
    hb_sample_t * dst, 
    hb_sample_t * src, 
    int nchans_out, 
    int nchans_in, 
    int nsamples, 
    hb_sample_t (*matrix)[8],
    hb_sample_t bias)
{
    int nn, ii, jj;
    hb_sample_t val;

    for (nn = 0; nn < nsamples; nn++)
    {
        for (ii = 0; ii < nchans_out; ii++)
        {
            val = 0;
            for (jj = 0; jj < nchans_in; jj++)
            {
                val += src[jj] * matrix[jj][ii];
            }
            dst[ii] = val + bias;
        }
        src += nchans_in;
        dst += nchans_out;
    }
}

static void set_level( hb_downmix_t * downmix )
{
    int ii, jj;
    int layout_in, layout_out;
    int mode_in;
    int mode_out;

    mode_in = downmix->mode_in & ~DOWNMIX_FLAGS_MASK;
    mode_out = downmix->mode_out & ~DOWNMIX_FLAGS_MASK;

    for (ii = 0; ii < 8; ii++)
    {
        for (jj = 0; jj < 8; jj++)
        {
            downmix->matrix[ii][jj] *= downmix->level;
        }
    }
    if (mode_out >= DOWNMIX_DOLBY)
        return;

    layout_in = channel_layout_map[mode_in];
    layout_out = channel_layout_map[mode_out];

    if (layout_in & HB_CH_FRONT_CENTER)
    {
        if (!(layout_out & HB_CH_FRONT_CENTER))
        {
            for (jj = 0; jj < 8; jj++)
            {
                downmix->matrix[downmix->center][jj] *= downmix->clev;
            }
        }
    }
    if (layout_in & (HB_CH_BACK_LEFT|HB_CH_BACK_RIGHT|HB_CH_BACK_CENTER|HB_CH_SIDE_LEFT|HB_CH_SIDE_RIGHT))
    {
        if (layout_out & (HB_CH_BACK_LEFT|HB_CH_BACK_RIGHT|HB_CH_BACK_CENTER|HB_CH_SIDE_LEFT|HB_CH_SIDE_RIGHT))
        {
            // Note, slev only gets set if input has surround, and output has none.
            return;
        }
    }
    for (jj = 0; jj < 8; jj++)
    {
        if ( downmix->left_surround >= 0 )
            downmix->matrix[downmix->left_surround][jj] *= downmix->slev;
        if ( downmix->right_surround >= 0 )
            downmix->matrix[downmix->right_surround][jj] *= downmix->slev;
        if ( downmix->rear_left_surround >= 0 )
            downmix->matrix[downmix->rear_left_surround][jj] *= downmix->slev;
        if ( downmix->rear_right_surround >= 0 )
            downmix->matrix[downmix->rear_right_surround][jj] *= downmix->slev;
    }
}

#define MIXMODE(x,y) (((x)<<4)|(y))
// The downmix operation can result in new sample values that are
// outside the original range of sample values.  If you wish to
// guarantee that the levels to not exceed the original range,
// call this function after initializing downmix and setting 
// your initial levels.
//
// Note that this can result in generally lower volume levels
// in the resulting downmixed audio.
void hb_downmix_adjust_level( hb_downmix_t * downmix )
{
    int mode_in, mode_out;
    hb_sample_t level = downmix->level;
    hb_sample_t clev = downmix->clev;
    hb_sample_t slev = downmix->slev;

    mode_in = downmix->mode_in & DOWNMIX_CHANNEL_MASK;
    mode_out = downmix->mode_out & DOWNMIX_CHANNEL_MASK;

    switch MIXMODE(mode_in, mode_out)
    {
    case MIXMODE(DOWNMIX_STEREO, DOWNMIX_MONO):
    case MIXMODE(DOWNMIX_2F2R, DOWNMIX_2F1R):
    case MIXMODE(DOWNMIX_2F2R, DOWNMIX_3F1R):
    case MIXMODE(DOWNMIX_3F2R, DOWNMIX_3F1R):
    case MIXMODE(DOWNMIX_3F4R, DOWNMIX_3F1R):
    case MIXMODE(DOWNMIX_3F4R, DOWNMIX_3F2R):
    level_3db:
        level /= LVL_PLUS3DB;
        break;

    case MIXMODE(DOWNMIX_3F, DOWNMIX_MONO):
        level /= LVL_PLUS3DB + clev * LVL_PLUS3DB;
        break;

    case MIXMODE(DOWNMIX_3F2R, DOWNMIX_2F1R):
    case MIXMODE(DOWNMIX_3F4R, DOWNMIX_2F1R):
    case MIXMODE(DOWNMIX_3F4R, DOWNMIX_2F2R):
        if (1 + clev < LVL_PLUS3DB)
            goto level_3db;
    case MIXMODE(DOWNMIX_3F, DOWNMIX_STEREO):
    case MIXMODE(DOWNMIX_3F, DOWNMIX_2F1R):
    case MIXMODE(DOWNMIX_3F, DOWNMIX_2F2R):
    case MIXMODE(DOWNMIX_3F, DOWNMIX_DOLBY):
    case MIXMODE(DOWNMIX_3F, DOWNMIX_DPLII):
    case MIXMODE(DOWNMIX_3F1R, DOWNMIX_2F1R):
    case MIXMODE(DOWNMIX_3F1R, DOWNMIX_2F2R):
    case MIXMODE(DOWNMIX_3F2R, DOWNMIX_2F2R):
        level /= 1 + clev;
        break;


    case MIXMODE(DOWNMIX_2F1R, DOWNMIX_MONO):
        level /= LVL_PLUS3DB + LVL_3DB * clev;
        break;

    case MIXMODE(DOWNMIX_2F1R, DOWNMIX_DOLBY):
        level /= 1 + LVL_3DB;
        break;

    case MIXMODE(DOWNMIX_2F1R, DOWNMIX_STEREO):
    case MIXMODE(DOWNMIX_2F1R, DOWNMIX_3F):
    case MIXMODE(DOWNMIX_3F1R, DOWNMIX_3F):
        level /= 1 + LVL_3DB * slev;
        break;

    case MIXMODE(DOWNMIX_3F1R, DOWNMIX_MONO):
        level /= LVL_PLUS3DB + LVL_PLUS3DB * clev + LVL_3DB * slev;
        break;

    case MIXMODE(DOWNMIX_3F1R, DOWNMIX_STEREO):
        level /= 1 + clev + LVL_3DB * slev;
        break;

    case MIXMODE(DOWNMIX_3F1R, DOWNMIX_DOLBY):
    case MIXMODE(DOWNMIX_3F1R, DOWNMIX_DPLII):
    case MIXMODE(DOWNMIX_2F2R, DOWNMIX_DOLBY):
        level /= 1 + LVL_PLUS3DB;
        break;

    case MIXMODE(DOWNMIX_2F2R, DOWNMIX_MONO):
        level /= LVL_PLUS3DB + LVL_PLUS3DB * slev;
        break;

    case MIXMODE(DOWNMIX_2F2R, DOWNMIX_STEREO):
    case MIXMODE(DOWNMIX_2F2R, DOWNMIX_3F):
    case MIXMODE(DOWNMIX_3F2R, DOWNMIX_3F):
        level /= 1 + slev;
        break;

    case MIXMODE(DOWNMIX_2F2R, DOWNMIX_DPLII):
        level /= 1 + LVL_SQRT_1_4 + LVL_SQRT_3_4;
        break;

    case MIXMODE(DOWNMIX_3F2R, DOWNMIX_MONO):
    case MIXMODE(DOWNMIX_3F4R, DOWNMIX_MONO):
        level /= LVL_PLUS3DB + LVL_PLUS3DB * clev * LVL_PLUS3DB * slev;
        break;

    case MIXMODE(DOWNMIX_3F2R, DOWNMIX_STEREO):
        level /= 1 + clev + slev;
        break;

    case MIXMODE(DOWNMIX_3F2R, DOWNMIX_DOLBY):
        level /= 1 + 3 * LVL_3DB;
        break;

    case MIXMODE(DOWNMIX_3F2R, DOWNMIX_DPLII):
        level /= 1 + LVL_3DB + LVL_SQRT_1_4 + LVL_SQRT_3_4;
        break;

    case MIXMODE(DOWNMIX_3F4R, DOWNMIX_STEREO):
        level /= 1 + clev + LVL_PLUS3DB * slev;
        break;

    case MIXMODE(DOWNMIX_3F4R, DOWNMIX_3F):
        level /= 1 + LVL_PLUS3DB * slev;
        break;

    case MIXMODE(DOWNMIX_3F4R, DOWNMIX_DOLBY):
        level /= 1 + 5 * LVL_3DB;
        break;

    case MIXMODE(DOWNMIX_3F4R, DOWNMIX_DPLII):
        level /= 1 + LVL_3DB + 2 * LVL_SQRT_1_4 + 2 * LVL_SQRT_3_4;
    }

    downmix->level = level;
    downmix->matrix_initialized = 0;
}

void hb_downmix_set_bias( hb_downmix_t * downmix, hb_sample_t bias )
{
    downmix->bias = bias;
}

// Changes the downmix mode if it needs changing after initialization
static void set_mode( hb_downmix_t * downmix )
{
    int ii, jj;
    int mode_in, mode_out;
    hb_sample_t (*matrix)[8];

    mode_in = downmix->mode_in & ~DOWNMIX_FLAGS_MASK;
    mode_out = downmix->mode_out & ~DOWNMIX_FLAGS_MASK;

    matrix = downmix_matrix[mode_in][mode_out];

    for (ii = 0; ii < 8; ii++)
    {
        for (jj = 0; jj < 8; jj++)
        {
            downmix->matrix[ii][jj] = matrix[ii][jj];
        }
    }
}

// Changes the downmix mode if it needs changing after initialization
int hb_downmix_set_mode( hb_downmix_t * downmix, int layout, int mixdown )
{
    int lfe_in, lfe_out;
    int mode_in, mode_out;

    if ( downmix == NULL )
        return -1;

    mode_in = hb_layout_to_mode(layout);
    mode_out = hb_mixdown_to_mode(mixdown);
    downmix->mode_in = mode_in;
    downmix->mode_out = mode_out;

    mode_in = downmix->mode_in & ~DOWNMIX_FLAGS_MASK;
    mode_out = downmix->mode_out & ~DOWNMIX_FLAGS_MASK;

    if (mode_in >= DOWNMIX_NUM_MODES || mode_out >= DOWNMIX_NUM_MODES)
        return -1;

    lfe_in = ((downmix->mode_in & DOWNMIX_LFE_FLAG) != 0);
    lfe_out = ((downmix->mode_out & DOWNMIX_LFE_FLAG) != 0);

    downmix->nchans_in = nchans_tbl[mode_in] + lfe_in;
    downmix->nchans_out = nchans_tbl[mode_out] + lfe_out;

    downmix->matrix_initialized = 0;
    return 0;
}

// Changes the downmix levels if they need changing after initialization
void hb_downmix_set_level( hb_downmix_t * downmix, hb_sample_t clev, hb_sample_t slev, hb_sample_t level )
{
    if ( downmix == NULL )
        return;

    downmix->clev = clev;
    downmix->slev = slev;
    downmix->level = level;
    downmix->matrix_initialized = 0;
}

static void set_chan_map( hb_downmix_t * downmix )
{
    int nchans;
    int ii, jj;
    int lfe;
    int * map;
    int * inv_map;
    int mode;
    hb_sample_t matrix[8][8];

    // Copy the matrix
    for ( ii = 0; ii < 8; ii++ )
    {
        for ( jj = 0; jj < 8; jj++ )
        {
            matrix[ii][jj] = downmix->matrix[ii][jj];
        }
    }

    // Rearrange the rows to correspond to the input channel order
    lfe = ((downmix->mode_in & DOWNMIX_LFE_FLAG) != 0);
    mode = downmix->mode_in & DOWNMIX_CHANNEL_MASK;
    nchans = nchans_tbl[mode] + lfe;
    map = downmix->map_in.chan_map[mode][lfe];
    inv_map = hb_qt_chan_map.inv_chan_map[mode][lfe];

    downmix->center = -1;
    downmix->left_surround = -1;
    downmix->right_surround = -1;
    downmix->rear_left_surround = -1;
    downmix->rear_right_surround = -1;
    for ( ii = 0; ii < nchans; ii++ )
    {
        int ord = map[ii];
        int row = inv_map[ord];
        switch (ord)
        {
            case CH_C:
                downmix->center = ii;
                break;
            case CH_LS:
                downmix->left_surround = ii;
                break;
            case CH_RS:
                downmix->right_surround = ii;
                break;
            case CH_Rls:
                downmix->rear_right_surround = ii;
                break;
            case CH_Rrs:
                downmix->rear_left_surround = ii;
                break;
        }
        for ( jj = 0; jj < 8; jj++ )
        {
            downmix->matrix[ii][jj] = matrix[row][jj];
        }
    }

    // Copy the matrix
    for ( ii = 0; ii < 8; ii++ )
    {
        for ( jj = 0; jj < 8; jj++ )
        {
            matrix[ii][jj] = downmix->matrix[ii][jj];
        }
    }

    // Rearrange the columns to correspond to the output channel order
    lfe = ((downmix->mode_out & DOWNMIX_LFE_FLAG) != 0);
    mode = downmix->mode_out & DOWNMIX_CHANNEL_MASK;
    nchans = nchans_tbl[mode] + lfe;
    map = downmix->map_out.chan_map[mode][lfe];
    inv_map = hb_qt_chan_map.inv_chan_map[mode][lfe];
    for ( ii = 0; ii < nchans; ii++ )
    {
        int ord = map[ii];
        int col = inv_map[ord];
        for ( jj = 0; jj < 8; jj++ )
        {
            downmix->matrix[jj][ii] = matrix[jj][col];
        }
    }
}

void hb_downmix_set_chan_map( 
    hb_downmix_t * downmix, 
    hb_chan_map_t * map_in, 
    hb_chan_map_t * map_out )
{
    downmix->map_in = *map_in;
    downmix->map_out = *map_out;
    downmix->matrix_initialized = 0;
}

hb_downmix_t * hb_downmix_init(int layout, int mixdown)
{
    hb_downmix_t * downmix = calloc(1, sizeof(hb_downmix_t));
    
    if (downmix == NULL)
        return NULL;
    if ( hb_downmix_set_mode( downmix, layout, mixdown ) < 0 )
    {
        free( downmix );
        return NULL;
    }
    // Set some good default values
    hb_downmix_set_level( downmix, LVL_3DB, LVL_3DB, 1.0 );
    downmix->bias = 0.0;
    downmix->matrix_initialized = 0;
    // The default input and output channel order is QT
    hb_downmix_set_chan_map( downmix, &hb_qt_chan_map, &hb_qt_chan_map );
    return downmix;
}

void hb_downmix_close( hb_downmix_t **downmix )
{
    if (*downmix != NULL)
        free(*downmix);
    *downmix = NULL;
}

static void init_matrix( hb_downmix_t * downmix )
{
    if ( !downmix->matrix_initialized )
    {
        set_mode( downmix );
        set_chan_map( downmix );
        set_level(downmix);
        downmix->matrix_initialized = 1;
    }
}

void hb_downmix( hb_downmix_t * downmix, hb_sample_t * dst, hb_sample_t * src, int nsamples)
{
    init_matrix( downmix );
    matrix_mul( dst, src, downmix->nchans_out, downmix->nchans_in, 
               nsamples, downmix->matrix, downmix->bias );
}

int hb_need_downmix( int layout, int mixdown )
{
    int mode_in, mode_out;

    mode_in = hb_layout_to_mode(layout);
    mode_out = hb_mixdown_to_mode(mixdown);

    return (mode_in != mode_out);
}
